package Math::Calculus::Secant;

use strict;
use warnings;

use Moose;
use Math::Calculus::Expression;

our $VERSION = '0.01';

#TODO reclamar se a exp tiver mais de uma var (die)
has expression => ( isa => 'Str', is => 'rw', coerce => 1, required => 1 );

#TODO reclamar se a variável não corresponder à da exp (die)
has variable => ( isa => 'Str', is => 'rw', required => 1 );
has xzero    => ( isa => 'Num', is => 'rw', required => 1 );
has xone     => ( isa => 'Num', is => 'rw', required => 1 );

#TODO receber dois valores de precisão: i. para f(x); ii. para o intervalo (xone - xzero e xtwo - xone )
has precision => ( isa => 'Int', is => 'rw', required => 1 );

#atualmente apenas para o valor de f(x)

has partial_results => ( is => 'rw', default => sub { [] } );

sub run {
    my $self = shift;
    my $exp  = Math::Calculus::Expression->new;
    $exp->addVariable( my $var = $self->variable ) or die $exp->getError;
    $exp->setExpression( $self->expression );
    my ( $xzero, $xone, $e )
        = ( $self->xzero, $self->xone, 10**-$self->precision );
    my ( $fx_zero, $fx_one )
        = ( $exp->evaluate( $var => $xzero ),
        $exp->evaluate( $var => $xone ) );
    push @{ $self->partial_results }, { x => $xzero, y => $fx_zero };
    push @{ $self->partial_results }, { x => $xone,  y => $fx_one };
    my $xtwo = _xtwo( $xzero, $fx_zero, $xone, $fx_one );
    my $fx_two = $exp->evaluate( $var => $xtwo );
    push @{ $self->partial_results }, { x => $xtwo, y => $fx_two };

    while ( abs $fx_zero > $e && abs $fx_one > $e && abs $fx_two > $e ) {
        $xtwo    = _xtwo( $xzero, $fx_zero, $xone, $fx_one );
        $xzero   = $xone;
        $xone    = $xtwo;
        $fx_zero = $exp->evaluate( $var => $xzero );
        $fx_one  = $exp->evaluate( $var => $xone );
        $fx_two  = $exp->evaluate( $var => $xtwo );
    }
    if ( abs $fx_zero <= $e ) {
        return $xzero;
    }
    elsif ( abs $fx_one <= $e ) {
        return $xone;
    }
    return $xtwo;
}

sub _xtwo {
    my ( $xzero, $fx_zero, $xone, $fx_one ) = @_;
    return ( $xzero * $fx_one - $xone * $fx_zero ) / ( $fx_one - $fx_zero );
}

1;
__END__

=head1 NAME

Math::Calculus::Secant - Perl extension for blah blah blah

=head1 SYNOPSIS

  use Math::Calculus::Secant;
  blah blah blah

=head1 DESCRIPTION

Stub documentation for Math::Calculus::Secant, created by h2xs. It looks like the
author of the extension was negligent enough to leave the stub
unedited.

Blah blah blah.

=head2 EXPORT

None by default.

=head1 SEE ALSO

Math::Calculus::NewtonRaphson

=head1 AUTHORS

Wallace Reis E<lt>wreis@cpan.orgE<gt>, Edem Cardim E<lt>edenc@cpan.orgE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2007 by Wallace Reis

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.8 or,
at your option, any later version of Perl 5 you may have available.


=cut
